package com.gsoe.problem.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

import com.gsoe.base.enumeration.ProblemType;
import com.gsoe.base.enumeration.TestStatus;
import com.gsoe.base.exception.ProblemException;
import com.gsoe.base.model.MqMessage;
import com.gsoe.base.model.User;
import com.gsoe.base.utils.UserUtil;
import com.gsoe.problem.dto.SaveJudgeTestDTO;
import com.gsoe.problem.dto.SubmitDTO;
import com.gsoe.problem.po.*;
import com.gsoe.problem.dto.TestDTO;
import com.gsoe.problem.mapper.TestMapper;

import com.gsoe.problem.service.ProblemAnswerService;
import com.gsoe.problem.service.ProblemService;
import com.gsoe.problem.service.TestProblemService;
import com.gsoe.problem.service.TestService;
import com.gsoe.problem.vo.SubmitVO;
import com.gsoe.problem.vo.TestVO;
import org.apache.commons.lang3.StringUtils;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.*;

/**
 * <p>
 * 测验表 服务实现类
 * </p>
 *
 * @author xiaoma
 * @since 2024-10-27
 */
@Service
public class TestServiceImpl extends ServiceImpl<TestMapper, Test> implements TestService {
    @Autowired
    private TestMapper testMapper;

    @Autowired
    private TestProblemService testProblemService;

    @Autowired
    ProblemAnswerService problemAnswerService;

    @Autowired
    ProblemService problemService;

    @Autowired
    StringRedisTemplate stringRedisTemplate;

    @Autowired
    RabbitTemplate rabbitTemplate;


    @Override
    @Transactional
    public Long createTest(TestDTO testDTO) {
        Test test = new Test();
        User loginUser = UserUtil.getLoginUser();
        test.setTestName(testDTO.getTestName());
        test.setUserId(loginUser.getId());
        test.setUserName(loginUser.getUserName());
        test.setStatus(TestStatus.JOIN);
        test.setTime(testDTO.getTime());

        //基于mysql排他锁原理防止高并发状态下同一个数据的多次插入
        Integer i = testMapper.insertWithLock(test);
        if(i<=0 || test.getId()==null){
            throw new ProblemException("创建失败,请检查测验名是否重复");
        }
        RandProblem randProblem =new RandProblem();
        BeanUtils.copyProperties(testDTO, randProblem);
        //随机组卷
        randProblem.setSingleChooseNum(15);
        randProblem.setMultipleChooseNum(5);
        randProblem.setJudgeNum(10);
        randProblem.setFillNum(5);
        randProblem.setBigNum(4);
        List<Long>problemIds=problemService.createRandProblemList(randProblem);

        List<TestProblem> testProblemList = problemIds.stream().map(l -> {
            TestProblem testProblem = new TestProblem();
            testProblem.setProblemId(l);
            testProblem.setTestId(test.getId());
            return testProblem;
        }).toList();

        testProblemService.saveBatch(testProblemList);
        //延迟消息，到时自动交卷

        SubmitDTO submitDTO = new SubmitDTO();
        submitDTO.setTextId(test.getId());
        submitDTO.setLoginId(loginUser.getId());
        submitDTO.setLoginName(loginUser.getUserName());

        rabbitTemplate.convertAndSend("test.direct", "TextSubmit", submitDTO, new MessagePostProcessor() {
            @Override
            public Message postProcessMessage(Message message) throws AmqpException {
                message.getMessageProperties().setDelay(testDTO.getTime()*60*1000);
                return message;
            }
        });
        return test.getId();
    }

    @Override
    public List<TestVO> getTextList(String textName,Long textId,Integer status,Long userId) {
        if(userId==null){userId=UserUtil.getLoginUser().getId();}
        return list(new QueryWrapper<Test>()
                .eq("user_id",userId)
                .eq(textId!=null,"id",textId)
                .eq(status!=null,"status",status)

                .like(StringUtils.isNotEmpty(textName),"test_name",textName)
                .orderByDesc("create_time")
        ).stream().map(test -> {
            TestVO testVO = new TestVO();
            if(Objects.equals(test.getStatus(), TestStatus.JOIN) && test.getCreateTime().plusMinutes(test.getTime()).isBefore(LocalDateTime.now())) {
                test.setStatus(TestStatus.FAIL);
                updateById(test);
            }
            BeanUtils.copyProperties(test,testVO);


            return testVO;
        }).toList();
    }

    @Override
    public Long getTextRemainingTime(Long textId) {
        if(textId==null){throw new ProblemException("测验id不能为空");}
        Test test = getById(textId);
        if(test==null){throw new ProblemException("无此测验id");}
        if(!test.getStatus().equals(TestStatus.JOIN)){
            return -1L;
        }
        return Duration.between(LocalDateTime.now(),test.getCreateTime()).toSeconds()+60L*test.getTime();
    }


    @Override
    public SubmitVO submit(SubmitDTO dto) {
        if(dto.getTextId()==null)throw new ProblemException("submit:id不能为null");

        if(dto.getLoginId()==null){
            User loginUser = UserUtil.getLoginUser();
            dto.setLoginId(loginUser.getId());
            dto.setLoginName(loginUser.getUserName());
        }
        Test test = getById(dto.getTextId());
        if(test==null){throw new ProblemException("无此测验");}
        if(dto.getInputMap()==null){
            test.setStatus(3);
            test.setScore(0);
            updateById(test);
            SubmitVO submitVO=new SubmitVO();
            submitVO.setScore(0);
            return submitVO;
        }
        //加乐观锁防止重复判题
        Integer b=testMapper.updateStatusByLock(dto.getTextId());

        if(b<1){
            SubmitVO submitVO = new SubmitVO();
            submitVO.setScore(test.getScore());
            return submitVO;
        }
        //为什么不能从输入那获取题目列表 而要重新从test_problem中获取？
        //前端传来的数据不可信，可能会多出原本不存在的题目，导致分数变高
        CheckAnswer checkAnswer=new CheckAnswer();
        List<Long> problemIDList = testProblemService.lambdaQuery()
                .eq(TestProblem::getTestId, dto.getTextId())
                .list().stream().map(TestProblem::getProblemId).toList();


        if(CollectionUtil.isEmpty(problemIDList)){
            SubmitVO submitVO = new SubmitVO();
            submitVO.setScore(0);
            return submitVO;
        }
        checkAnswer.setProblemList(
                problemService.lambdaQuery()
                .in(Problem::getId,problemIDList)
                .list()
        );
        checkAnswer.setProblemIDAndInput(dto.getInputMap());
        checkAnswer.setSingleScore(2);
        checkAnswer.setMultiScore(3);
        checkAnswer.setJudgeScore(2);
        checkAnswer.setFillScore(3);
        checkAnswer.setBigScore(5);

        SubmitVO submitVO = new SubmitVO();
        SaveJudgeTestDTO saveJudgeTestDTO = getScoreWithCheckAnswer(checkAnswer);
        submitVO.setScore(saveJudgeTestDTO.getScore());
        //4: id
        //5: 姓名
        //6: testId
        saveJudgeTestDTO.setLoginId(dto.getLoginId());
        saveJudgeTestDTO.setLoginName(dto.getLoginName());
        saveJudgeTestDTO.setTestId(dto.getTextId());
        rabbitTemplate.convertAndSend("judge.direct","saveJudgeTest",saveJudgeTestDTO);
        test.setScore(submitVO.getScore());
        test.setStatus(TestStatus.FINISH);
        updateById(test);
        return submitVO;
    }

    @Override
    public void reSubmit(Long textId) {
        User loginUser = UserUtil.getLoginUser();
        Test test = lambdaQuery()
                .eq(Test::getId, textId)
                .eq(Test::getUserId, loginUser.getId())
                .one();

        if(test==null){throw new ProblemException("ReSubmit:无此测验记录");}
        if(test.getRemainingCount()<=0){throw new ProblemException("ReSubmit:剩余次数不足");}
        if(test.getStatus().equals(TestStatus.JOIN)){throw new ProblemException("ReSubmit:测验正在进行,请结束后重试");}
        if(test.getStatus().equals(TestStatus.JUDGEING)){throw new ProblemException("ReSubmit:评测中，请稍后重试");}
        //这里为什么是加入而不是评测中？
        //因为在消费者那，会用乐观锁判断 如果是评测中，将不执行评测逻辑
        //因此这里虽然是重新评测，但仍先设置为加入中，给评测消费者后，将状态改为评测中，完成后再改为已完成
        test.setStatus(TestStatus.JOIN);
        test.setRemainingCount(test.getRemainingCount()-1);
        updateById(test);

        SubmitDTO submitDTO = new SubmitDTO();
        submitDTO.setTextId(test.getId());
        submitDTO.setLoginId(loginUser.getId());
        submitDTO.setLoginName(loginUser.getUserName());
        rabbitTemplate.convertAndSend("test.direct", "TextSubmit", submitDTO, new MessagePostProcessor() {
            @Override
            public Message postProcessMessage(Message message) throws AmqpException {
                message.getMessageProperties().setDelay(1);
                return message;
            }
        });

    }

    //1：总分
    //2：细分
    //3: 输入
    public SaveJudgeTestDTO getScoreWithCheckAnswer(CheckAnswer checkAnswer){
        Integer score = 0;
        Map<Long,Integer>problemIdWithScore=new HashMap<>();
        Map<Long,String>problemIdWithInput=new HashMap<>();
        List<Problem> problemList = checkAnswer.getProblemList();
        Map<Long, String> problemIDAndInput = checkAnswer.getProblemIDAndInput();
        for (Problem p : problemList) {
            String input = problemIDAndInput.get(p.getId());
            List<String> answerList = problemAnswerService.lambdaQuery()
                    .eq(ProblemAnswer::getProblemId, p.getId())
                    .list().stream().map(ProblemAnswer::getAnswer).toList();
            int t=0;
            //如果没有答案或该题被删除，给予满分
            if(CollectionUtil.isEmpty(answerList) || answerList.contains(input)){
                if(p.getType().equals(ProblemType.SINGLE))t=checkAnswer.getSingleScore();
                else if(p.getType().equals(ProblemType.MULTIPLE))t=checkAnswer.getMultiScore();
                else if(p.getType().equals(ProblemType.BIG))t=checkAnswer.getBigScore();
                else if(p.getType().equals(ProblemType.JUDGE))t=checkAnswer.getJudgeScore();
                else if(p.getType().equals(ProblemType.FILL))t=checkAnswer.getFillScore();
            }
            score+=t;
            problemIdWithScore.put(p.getId(),t);
            problemIdWithInput.put(p.getId(),input);
        }
        SaveJudgeTestDTO saveJudgeTestDTO=new SaveJudgeTestDTO();
        saveJudgeTestDTO.setScore(score);
        saveJudgeTestDTO.setProblemIdWithScore(problemIdWithScore);
        saveJudgeTestDTO.setProblemIdWithInput(problemIdWithInput);
        return  saveJudgeTestDTO;
    }
}
